#include "VxIrrLib.h"

//------------------------------------------------------------------------------
//=== constructor ===//
EntBase::EntBase( VxIrrBase * poIrrBase )
	: m_poIrrBase(poIrrBase)
	, m_s32EntId(0)					// id of this entity
	, m_s32OwnerId(0)				// id of owner of this entity
	, m_s32TemplateId(0)			// id of entity template
	, m_eEntType( eEntityTypeInvalid )					// type of entity

	//=== collision vars ===//
	, m_bTouchingGround(0)				// object is touching ground
	, m_bTouchingWall(0)				// object is touching wall
	//irr::core::stringc			m_strScriptCollisionCallback;	// call script function on collision

	//=== Physics vars ===//
	, m_poRigidBody(0)				// physics body
	, m_vec3LastPosition( 0.0f, 0.0f, 0.0f )			// last postition of entity
	, m_vec3InterpolatedPosition( 0.0f, 0.0f, 0.0f )	// interpolated postition of entity
	, m_u32LastCollisionTimeMs(0)

	//=== graphics vars ===//
	, m_poSceneNode(0)
	, m_poMesh(0)

	//=== state vars ===//
	, m_bDeleted(false)					// has been deleted or about to be deleted
	, m_s32EntAliveTimer(0)				// time in millisec since entity was created
	, m_s32LifeTimeMs(0)				// if not zero then time in ms entity should live
	, m_s32PowerLevel(0)				// power up level
	, m_s32PowerUpTTL(0)				// time power up has to live in seconds
	, m_iTimeInStartPos(0)
{
	// empty
}
//------------------------------------------------------------------------------
//=== destructor ===//
EntBase::~EntBase()
{
	if( m_poIrrBase )
	{
		if( m_poSceneNode )
		{
			m_poSceneNode->remove();
			m_poSceneNode = NULL;
		}
		if( m_poRigidBody ) 
		{
			m_poIrrBase->m_poPhysBase->GetPhysWorld()->removeRigidBody( m_poRigidBody );
			delete m_poRigidBody->getCollisionShape();
			delete m_poRigidBody->getMotionState();
			delete m_poRigidBody;
			m_poRigidBody = NULL;
		}
		m_poIrrBase = NULL;
	}
}

//------------------------------------------------------------------------------
//! update
void EntBase::Update( U32 u32FrameTimeMs )
{
	m_s32EntAliveTimer += u32FrameTimeMs;

	// Check for expiration
	if( ( m_s32LifeTimeMs > 0.0f ) && ( m_s32EntAliveTimer > m_s32LifeTimeMs ) )
	{
		// mark to be deleted 
		m_bDeleted = true;
	}
}


//------------------------------------------------------------------------------
//! updates the position and rotation for graphical render of the entity
void EntBase::UpdateRender( void) 
{
	if( m_poRigidBody && m_poSceneNode ) 
	{
		// get current physics transform
		btTransform oWorldTransform;
		m_poRigidBody->getMotionState()->getWorldTransform( oWorldTransform );

		// set model position
		m_poSceneNode->setPosition( irr::core::vector3df(	(irr::f32)m_vec3InterpolatedPosition[0], 
															(irr::f32)m_vec3InterpolatedPosition[1], 
															(irr::f32)m_vec3InterpolatedPosition[2]));
		// set model rotation
		btVector3 vec3EulerRotation;
		btQuaternion quatRigidRotation = oWorldTransform.getRotation();
		IrrPhysQuaternionToEuler( quatRigidRotation, vec3EulerRotation );
		m_poSceneNode->setRotation( irr::core::vector3df( vec3EulerRotation[0], vec3EulerRotation[1], vec3EulerRotation[2]) );
	}
}
//------------------------------------------------------------------------------
//! set speed and heading of entity movement
void EntBase::SetLinearVelocity( const btVector3& vec3Velocity )
{ 
	m_poRigidBody->setLinearVelocity( vec3Velocity ); 
	m_poRigidBody->activate( true ); // force entity to be active so we get the MotionState::setWorldTransform call
}

//------------------------------------------------------------------------------
//! halt physics body movement
void EntBase::StopMovement( void )
{
	m_poRigidBody->setLinearVelocity( btVector3( 0.0f, 0.0f, 0.0f ) );
	m_poRigidBody->setAngularVelocity( btVector3( 0.0f, 0.0f, 0.0f ) );
}

//------------------------------------------------------------------------------
//! reset position of entity to starting position
void EntBase::ResetToStartingPosition( void )
{
	m_iTimeInStartPos = 0; // time in seconds asteroid has been in start position
	StopMovement();
	SetIrrPlusPhysicsPositionAndRotation( m_v3fStartPosition, m_v3fStartRotation );
}

//------------------------------------------------------------------------------
//! return true is scene node is this entities or any children like particle system etc
BOOL EntBase::IsMySceneNode( irr::scene::ISceneNode * poNode )
{
	if( poNode == m_poSceneNode )
	{
		return true;
	}
	for( U32 i = 0; i < m_aoChildNodes.size(); i++ )
	{
		if( poNode == m_aoChildNodes[i] )
		{
			return true;
		}
	}
	return false;
}


//------------------------------------------------------------------------------
//! interporlate between last and current positions
void EntBase::CalculateInterpolatedPosition( float f32BlendFactor ) 
{
	if( m_poRigidBody ) 
	{
		const btVector3& vec3CurrentPosition = GetPhysicsPosition();
		m_vec3InterpolatedPosition = vec3CurrentPosition * f32BlendFactor + m_vec3LastPosition * ( 1.0f - f32BlendFactor );
	}
}
//------------------------------------------------------------------------------
//! set position of last collision	
void EntBase::SetLastCollisionPosition( const irr::core::vector3df& v3fPos )
{
	m_v3fLastCollisionPosition = v3fPos;
	m_u32LastCollisionTimeMs = m_poIrrBase->m_u32GameTimeMs;
}
//------------------------------------------------------------------------------
//! get position of last collision	
irr::core::vector3df& EntBase::GetLastCollisionPosition( void )
{
	return m_v3fLastCollisionPosition;
}

//------------------------------------------------------------------------------
//! set physics position of entity using bullet coords
void EntBase::SetPhysicsPosition( const btVector3& vec3Position )
{
	m_vec3LastPosition = vec3Position;
	m_poRigidBody->getWorldTransform().setOrigin( vec3Position );
}
//------------------------------------------------------------------------------
//! set physics position of entity using irrlicht coords
void EntBase::SetPhysicsPosition( const irr::core::vector3df& v3fPosition )
{
	btVector3 vecBullet( v3fPosition.X, v3fPosition.Y, v3fPosition.Z );
	m_vec3LastPosition = vecBullet;
	m_poRigidBody->getWorldTransform().setOrigin( vecBullet );
}
//------------------------------------------------------------------------------
//! set physics position of entity using floats
void EntBase::SetPhysicsPosition( irr::f32 f32PosX, irr::f32 f32PosY, irr::f32 f32PosZ )
{
	btVector3 vecBullet( f32PosX, f32PosY, f32PosZ );
	m_vec3LastPosition = vecBullet;
	m_poRigidBody->getWorldTransform().setOrigin( vecBullet );
}

//------------------------------------------------------------------------------
//! set physics and graphical position of entity using irrlicht coords	
void EntBase::SetIrrPlusPhysicsPosition( const irr::core::vector3df& v3fPos )
{
	log_msg( 0, "EntBase::SetIrrPlusPhysicsPosition: %s X=%3.2f, Y=%3.2f, Z=%3.2f\n", 
			m_strEntProperName.c_str(), v3fPos.X, v3fPos.Y, v3fPos.Z );

	m_poRigidBody->getWorldTransform().setOrigin( btVector3( v3fPos.X, v3fPos.Y, v3fPos.Z ) );
	if( m_poSceneNode )
	{
		// if object is not active or has zero mass then the 
		// IrrPhysMotionState::setWorldTransform is never called and the graphical representation of the entity updated
		// so do it now
		m_poSceneNode->setPosition( v3fPos );
	}
	else
	{
		log_msg( 0, "EntBase::SetIrrPlusPhysicsPosition: NULL scene node\n" ); 
	}
}
//------------------------------------------------------------------------------
//! set physics and graphical position and rotation of entity using irrlicht coords		
void EntBase::SetIrrPlusPhysicsPositionAndRotation( const irr::core::vector3df& v3fPos, const irr::core::vector3df& v3fRot )
{
	btTransform oTrans;
	oTrans = m_poRigidBody->getWorldTransform();
	oTrans.setRotation(btQuaternion( v3fRot.Y * irr::core::DEGTORAD, v3fRot.X * irr::core::DEGTORAD, v3fRot.Z * irr::core::DEGTORAD) );
	oTrans.setOrigin( btVector3( v3fPos.X, v3fPos.Y, v3fPos.Z ) );
	m_poRigidBody->setWorldTransform( oTrans );
	if( m_poSceneNode )
	{
		// if object is not active or has zero mass then the 
		// IrrPhysMotionState::setWorldTransform is never called and the graphical representation of the entity updated
		// so do it now
		m_poSceneNode->setRotation( v3fRot );
		m_poSceneNode->setPosition( v3fPos );
	}

}

//------------------------------------------------------------------------------
//! get rotation of physics shape in irrlicht coords ( degrees )
void EntBase::GetPhysicsRotationInDegrees( irr::core::vector3df& v3fRetRot )
{
	btQuaternion oQuat = m_poRigidBody->getWorldTransform().getRotation();
	v3fRetRot.set( oQuat[0] * irr::core::RADTODEG, oQuat[1] * irr::core::RADTODEG, oQuat[2] * irr::core::RADTODEG );
}

//------------------------------------------------------------------------------
//! process before frame is rendered 
void EntBase::BeginFrame( void ) 
{
	// reset touch vars.. will get set during update
	m_bTouchingGround = false;
	m_bTouchingWall = false;
	if( m_poRigidBody ) 
	{
		m_vec3LastPosition = m_poRigidBody->getWorldTransform().getOrigin();
		m_quatLastRotation = m_poRigidBody->getWorldTransform().getRotation();
	}
}

//------------------------------------------------------------------------------
//! process after frame is rendered 
void EntBase::EndFrame( void ) 
{
	/*
	// send moved object to server
	if( m_poSceneNode && 
		m_poRigidBody && 
		( ( m_vec3LastPosition != GetTruePosition() ) || ( m_vec3LastRotation != GetRotation() ) ) ) 
	{
		// g_EntMgrImp.EntitiyMoveUpdate( this );
	}
	*/
}


//------------------------------------------------------------------------------
//! hande collision with another object
//! return non zero if caller should not process any more contact points
RCODE  EntBase::HandleCollision( EntBase * poOtherEntity, const btPersistentManifold * poContactManifold, float f32NormalScale )
{
	if( eEntityTypeZone != poOtherEntity->GetEntType() ) 
	{
		// walk the contacts and determine if we are touching wall or ground
		for( int i = 0; i < poContactManifold->getNumContacts(); i++ ) 
		{
			float f32NormalY = poContactManifold->getContactPoint(i).m_normalWorldOnB[1] * f32NormalScale;
			if(f32NormalY > 0.6f)
			{
				m_bTouchingGround = true;
			}
			if(  ( f32NormalY < 0.7f ) && ( f32NormalY > -0.7f ) )
			{
				m_bTouchingWall = true;
			}
		}
	}
	if( 0 == OnCollision( poOtherEntity, poContactManifold, f32NormalScale ) )
	{
	}
	// if script for collistion is specified then call it
	if( m_strScriptCollisionCallback.size() )
	{
		g_ScriptingImp.CallCollisionHandler( m_strScriptCollisionCallback, this, poOtherEntity );
	}
	return 0;
}

//------------------------------------------------------------------------------
//! called after hande collision has determined if touching ground or wall
//! return 0 if scripted collistion should be called else return error code
RCODE EntBase::OnCollision( EntBase * poOtherEntity, const btPersistentManifold * poContactManifold, float f32NormalScale )
{
	return 0;
}
